// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

/*
  This MUMPS Language script was constructed using vbscript.js as a template.
*/

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('mumps', function() {
    function wordRegexp(words) {
      return new RegExp('^((' + words.join(')|(') + '))\\b', 'i');
    }

    var singleOperators = new RegExp("^[\\+\\-\\*/&#!_?\\\\<>=\\'\\[\\]]");
    var doubleOperators = new RegExp(
      "^(('=)|(<=)|(>=)|('>)|('<)|([[)|(]])|(^$))",
    );
    var singleDelimiters = new RegExp('^[\\.,:]');
    var brackets = new RegExp('[()]');
    var identifiers = new RegExp('^[%A-Za-z][A-Za-z0-9]*');
    var commandKeywords = [
      'break',
      'close',
      'do',
      'else',
      'for',
      'goto',
      'halt',
      'hang',
      'if',
      'job',
      'kill',
      'lock',
      'merge',
      'new',
      'open',
      'quit',
      'read',
      'set',
      'tcommit',
      'trollback',
      'tstart',
      'use',
      'view',
      'write',
      'xecute',
      'b',
      'c',
      'd',
      'e',
      'f',
      'g',
      'h',
      'i',
      'j',
      'k',
      'l',
      'm',
      'n',
      'o',
      'q',
      'r',
      's',
      'tc',
      'tro',
      'ts',
      'u',
      'v',
      'w',
      'x',
    ];
    // The following list includes instrinsic functions _and_ special variables
    var intrinsicFuncsWords = [
      '\\$ascii',
      '\\$char',
      '\\$data',
      '\\$ecode',
      '\\$estack',
      '\\$etrap',
      '\\$extract',
      '\\$find',
      '\\$fnumber',
      '\\$get',
      '\\$horolog',
      '\\$io',
      '\\$increment',
      '\\$job',
      '\\$justify',
      '\\$length',
      '\\$name',
      '\\$next',
      '\\$order',
      '\\$piece',
      '\\$qlength',
      '\\$qsubscript',
      '\\$query',
      '\\$quit',
      '\\$random',
      '\\$reverse',
      '\\$select',
      '\\$stack',
      '\\$test',
      '\\$text',
      '\\$translate',
      '\\$view',
      '\\$x',
      '\\$y',
      '\\$a',
      '\\$c',
      '\\$d',
      '\\$e',
      '\\$ec',
      '\\$es',
      '\\$et',
      '\\$f',
      '\\$fn',
      '\\$g',
      '\\$h',
      '\\$i',
      '\\$j',
      '\\$l',
      '\\$n',
      '\\$na',
      '\\$o',
      '\\$p',
      '\\$q',
      '\\$ql',
      '\\$qs',
      '\\$r',
      '\\$re',
      '\\$s',
      '\\$st',
      '\\$t',
      '\\$tr',
      '\\$v',
      '\\$z',
    ];
    var intrinsicFuncs = wordRegexp(intrinsicFuncsWords);
    var command = wordRegexp(commandKeywords);

    function tokenBase(stream, state) {
      if (stream.sol()) {
        state.label = true;
        state.commandMode = 0;
      }

      // The <space> character has meaning in MUMPS. Ignoring consecutive
      // spaces would interfere with interpreting whether the next non-space
      // character belongs to the command or argument context.

      // Examine each character and update a mode variable whose interpretation is:
      //   >0 => command    0 => argument    <0 => command post-conditional
      var ch = stream.peek();

      if (ch == ' ' || ch == '\t') {
        // Pre-process <space>
        state.label = false;
        if (state.commandMode == 0) state.commandMode = 1;
        else if (state.commandMode < 0 || state.commandMode == 2)
          state.commandMode = 0;
      } else if (ch != '.' && state.commandMode > 0) {
        if (ch == ':') state.commandMode = -1;
        // SIS - Command post-conditional
        else state.commandMode = 2;
      }

      // Do not color parameter list as line tag
      if (ch === '(' || ch === '\u0009') state.label = false;

      // MUMPS comment starts with ";"
      if (ch === ';') {
        stream.skipToEnd();
        return 'comment';
      }

      // Number Literals // SIS/RLM - MUMPS permits canonic number followed by concatenate operator
      if (stream.match(/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/)) return 'number';

      // Handle Strings
      if (ch == '"') {
        if (stream.skipTo('"')) {
          stream.next();
          return 'string';
        } else {
          stream.skipToEnd();
          return 'error';
        }
      }

      // Handle operators and Delimiters
      if (stream.match(doubleOperators) || stream.match(singleOperators))
        return 'operator';

      // Prevents leading "." in DO block from falling through to error
      if (stream.match(singleDelimiters)) return null;

      if (brackets.test(ch)) {
        stream.next();
        return 'bracket';
      }

      if (state.commandMode > 0 && stream.match(command)) return 'variable-2';

      if (stream.match(intrinsicFuncs)) return 'builtin';

      if (stream.match(identifiers)) return 'variable';

      // Detect dollar-sign when not a documented intrinsic function
      // "^" may introduce a GVN or SSVN - Color same as function
      if (ch === '$' || ch === '^') {
        stream.next();
        return 'builtin';
      }

      // MUMPS Indirection
      if (ch === '@') {
        stream.next();
        return 'string-2';
      }

      if (/[\w%]/.test(ch)) {
        stream.eatWhile(/[\w%]/);
        return 'variable';
      }

      // Handle non-detected items
      stream.next();
      return 'error';
    }

    return {
      startState: function() {
        return {
          label: false,
          commandMode: 0,
        };
      },

      token: function(stream, state) {
        var style = tokenBase(stream, state);
        if (state.label) return 'tag';
        return style;
      },
    };
  });

  CodeMirror.defineMIME('text/x-mumps', 'mumps');
});
